/* Chibi IT Play  - Copyright (c) 2009, Juan Linietsky */
/*

License:

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
    * Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
    CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/


#include "itplay.h"
#include <stddef.h>

/* #define  IT_DEBUG */

#ifdef _IT_DEBUG_CUSTOM_H
	#include _IT_DEBUG_CUSTOM_H
#else
	#ifdef _IT_DEBUG
		#include <stdio.h>

		#define _IT_DEBUG_PRINTF( m_arg )\
		do {\
			fprintf(stdout , m_arg );\
		} while (0)

			#define _IT_ERROR_PRINT( m_arg )\
			do {\
				fprintf(stdout , "** %s:%i ** IT_PLAY ERROR:  ", __FILE__, __LINE__);\
				fprintf(stdout , m_arg );\
				fprintf(stdout , "\n");\
			} while (0)

	#else
		/* Or Implement as you wish */
		#define _IT_DEBUG_PRINT( m_arg ) \
		do { } while(0)
		#define _IT_ERROR_PRINT( m_arg ) \
		do { } while(0)

	#endif

#endif

#define _IT_ABS(m_v)  (((m_v)<0)?(-(m_v)):(m_v))
static void _it_zero_mem(void * p_mem, it_u32 p_bytes) {

	it_u8 *mem=(it_u8*)p_mem;
	int i;
	for (i=0;i<p_bytes;i++)
		mem[i]=0;
}

/** INSTANCE **/


typedef struct {
	
	IT_Mixer *mixer;
	IT_MemoryManager *memory;
	IT_FileIO *file_io;
	IT_AudioLock *audio_lock;

	IT_LoaderFunc loaders[IT_CONSTANT_MAX_LOADER_FUNCS];
	int loader_count;

} _IT_Instance;

IT_Mixer *it_instance_get_mixer(IT_Instance *p_instance) {

	_IT_Instance *instance = (_IT_Instance*)p_instance->_private;
	return instance->mixer;
}

IT_LoaderError it_instance_load_song(IT_Instance *p_instance,IT_Song *p_song,const char *p_filename,it_u8 p_loader_flags) {
	
	_IT_Instance *instance = (_IT_Instance*)p_instance->_private;
	int i;
	IT_LoaderError err;
	if (!instance) {
	
		_IT_ERROR_PRINT("Instance is NULL");
		return  IT_LOADER_UNCONFIGURED;
	}

	/* erase current song data */
	it_song_free_instruments(p_instance,p_song);
	it_song_free_music(p_instance,p_song);

	err=IT_LOADER_ERROR_FILE_UNRECOGNIZED;
	
	for(i=0;i<instance->loader_count;i++) {
		
		err = instance->loaders[i](p_song,p_instance,instance->file_io,instance->memory,p_filename,p_loader_flags);
		
		if (err==IT_LOADER_ERROR_FILE_UNRECOGNIZED)
			continue;
		else
			return err;
	}
	
	return err;
}

extern IT_LoaderFunc it_default_loaders[];

void it_instance_create(IT_Instance *r_instance, IT_MemoryManager *p_memory, IT_FileIO *p_file_io, IT_Mixer *p_mixer, IT_AudioLock *p_audio_lock,IT_LoaderFunc *p_loaders) {
	
	_IT_Instance *instance = p_memory->alloc( sizeof(_IT_Instance), IT_MEMORY_ALLOC_INSTANCE );
	IT_LoaderFunc *loaders=it_default_loaders;

	instance->mixer=p_mixer;
	instance->memory=p_memory;
	instance->file_io=p_file_io;
	instance->audio_lock=p_audio_lock;
	instance->loader_count=0;
	
	while(loaders && *loaders && instance->loader_count<IT_CONSTANT_MAX_LOADER_FUNCS) {

		instance->loaders[instance->loader_count++]=*loaders;
		loaders++;
	}

	while(p_loaders && *p_loaders && instance->loader_count<IT_CONSTANT_MAX_LOADER_FUNCS) {
		
		instance->loaders[instance->loader_count++]=*p_loaders;
		p_loaders++;
	}
			
	r_instance->_private=instance;

}
void it_instance_free(IT_Instance *p_instance)  {

	_IT_Instance *instance = (_IT_Instance*)p_instance->_private;
	if (!instance) {
		_IT_ERROR_PRINT("Instance is NULL");
		return;
	}
	
	if (instance->mixer)
		instance->mixer->reset_voices(instance->mixer);
		
	/* self erase.. */
	instance->memory->free( instance, IT_MEMORY_ALLOC_INSTANCE ); 
}

/** PLAYER **/

typedef enum {

	_IT_PLAYER_CONSTANT_MAX_VOICES=128,
	_IT_PLAYER_PAN_LEFT=0,
	_IT_PLAYER_PAN_RIGHT=255,
	_IT_PLAYER_PAN_SURROUND=512

} _IT_MixerConstants;

typedef struct {

	it_s16 tick; /* current tick */
	it_u8 current_point; /* current node where ticks belong to (for speed) */
	it_s16 value;
	it_bool done;

} _IT_EnvelopeProcess;

typedef struct {

	it_bool active;

	it_bool nna; /* has no master channel, is on nna */
	it_u8 master_channel;
	it_bool note_off;
	it_bool note_fade;

	it_u8 instrument;
	it_u8 sample;

	/* Variables upated from channel  */
	it_u8 note;
	it_u8 volume;
	it_u16 pan;
	it_s32 period;
	it_u16 fadeout;
	it_u8 chorus;
	it_u8 reverb;

	_IT_EnvelopeProcess volume_envelope;
	_IT_EnvelopeProcess pan_envelope;
	_IT_EnvelopeProcess pitch_filter_envelope;


} _IT_Voice;


typedef struct {

	it_u32 note_start_offset;

	it_s32 period; /* channel period  */
	it_s32 old_period; /* period before note */
	it_s32 tickrel_period; /* only for this tick, relative period added to output..  */
	it_s32 porta_period; /* target period for portamento  */

	it_u8 note; /* last note parsed */
	it_u8 instrument; /* last instrument parsed */
	it_u8 command;
	it_u8 parameter;

	it_bool active;
	it_bool note_off;
	it_u8 volume;
	it_u8 channel_volume;
	it_u16 pan;

	it_u8 chorus;
	it_u8 reverb;

	it_u8 sample;
	it_bool portamento_active;
	it_bool row_has_note;
	it_bool restart;

	it_u32 restart_offset;

	it_s16 tickrel_volume;
	it_s16 real_note; /* note + note adjustment in sample */
	it_s8 finetune; /* finetune used */
	it_u8 volume_command;
	it_u8 note_delay;

	it_bool has_slave_voice;
	it_u8 slave_voice;

} _IT_Channel;

typedef struct {

	it_u8 chan_mask[IT_CONSTANT_MAX_CHANNELS]; /* mask cache for each */
	it_u8 last_value[64][5];
	it_u8 current_pattern;
	it_u16 current_row;
	it_u32 data_pos;
	it_bool restart;

} _IT_PatternDecompressor;

typedef struct {

	IT_Player _player; /* inheritance */
	_IT_Instance *instance;

	_IT_Channel channels[IT_CONSTANT_MAX_CHANNELS];
	_IT_Voice *voices;
	it_u8 *voice_pool; /* pool for fetching free voices */
	it_u8 voice_count; /* amount of voices used */
	it_u8 voice_max;

	IT_Song *song;

	it_u8 tick_counter;
	it_u8 current_speed;
	it_u8 current_tempo;
	it_u8 global_volume;

	/* Position */

	it_u16 current_order;
	it_u16 current_row;
	it_u16 current_pattern;
	it_u16 pattern_delay;

	/* flags */

	it_bool force_next_order; /* force an order change, IT commands Cxx, Bxx */
	it_u8 forced_next_order; /* order change */
	it_u16 forced_next_order_row; /* order change */
	it_bool active;

	it_u32 random_seed;

	_IT_PatternDecompressor decompressor;

} _IT_PlayerData;




it_u32 _it_player_random(_IT_PlayerData *p) {

	it_u32 k;
	it_u32 s = p->random_seed;
	if (s == 0)
		s = 0x12345987;
	k = s / 127773;
	s = 16807 * (s - k * 127773) - 2836 * k;
	if (s < 0)
		s += 2147483647;
	p->random_seed = s;
	return s;
}

static it_u32 _it_player_get_linear_period(it_u16 note) {

	it_u32 t;

	t=(24*12-(it_u32)note*2)*32;
	return t;
}

static int _s3m_period_table[12]={1712,1616,1524,1440,1356,1280,1208,1140,1076,1016,960,907};


it_u32 _it_player_get_log_period(it_u16 note,it_u32 p_c5freq) {

	return (8363L * 16 * s3m_period_table[note%12] >> (note/12)) / (p_c5freq>>1);
}

static void _it_player_envelope_reset(_IT_EnvelopeProcess *p_env_process ) {

	p_env_process->tick=0;
	p_env_process->current_point=0;
	p_env_process->value=0;
	p_env_process->done=it_false;
}

static void _it_player_assign_slave_voice(_IT_PlayerData *p, it_u8 p_channel) {

	_IT_Channel *ch=&p->channels[p_channel];
	_IT_Voice *v;
	int found=-1;

	if (ch->has_slave_voice && !p->voices[ch->slave_voice].nna && p->voices[ch->slave_voice].master_channel==p_channel) {
		found=ch->slave_voice;
	}

	if (found==-1 && p->voice_count<p->voice_max) {
		/* assign an unused one */
		found=p->voice_count;
		p->voice_count++;
	}

	if (found==-1) {
		/* steal the one closer to die */
		it_u32 prio=0;
		it_u8 prio_idx=0;
		int i;

		for(i=0;i<p->voice_count;i++) {
			/* shitty priority computation, should do better... */
			it_u32 current_prio=0;
			current_prio=p->voices[i].volume;
			if (p->voices[i].note_fade)
				current_prio*=16;
			if (p->voices[i].note_off)
				current_prio*=16;

			if (prio<current_prio) {
				prio=current_prio;
				prio_idx=i;
			}
		}

		found=prio_idx;
	}


	v=p->voices[found];

	ch->slave_voice=found;
	ch->has_slave_voice=it_true;
	v->master_channel=p_channel;
	v->nna=it_false;
	v->note_off=it_false;
	v->note_fade=it_false;
	_it_player_envelope_reset(&v->volume_envelope);
	_it_player_envelope_reset(&v->pan_envelope);
	_it_player_envelope_reset(&v->pitch_filter_envelope);

}


static void _it_player_process_note(_IT_PlayerData *p, it_u8 p_channel, it_u8 p_note[5]) {

 /*printf("note chan: %i %i:%i:%i:%c:%x\n",channel,note[0],(note[1]==0?0:note[1]),note[2],(note[3]==0?' ':note[3]+'A'-1),note[4]);*/

	_IT_Channel *ch=&p->channels[p_channel];
	it_u8 sample=0;

	/* Validate instrument and note fields */

	if (p->song->flags&IT_SONG_FLAG_USE_INSTRUMENTS && p->song->instruments[ p_note[0] ]==NULL ) {

		p_note[0]=IT_CONSTANTnote.instrument=_IT_FIELD_EMPTY;
		note.note=_IT_FIELD_EMPTY;

	}

	if (p_note[0]>IT_CONSTANT_MAX_NOTES && p_note[0]<254)  {
		/* if note is invalid, both become empty */
		p_note[0]=IT_CONSTANT_MAX_NOTES;
	}

	/* Determine wether note should be processed */

	if (p_note[0]<IT_CONSTANT_MAX_NOTES || (p_note[1]>0 && p_note[0]==IT_CONSTANT_MAX_NOTES))  {

		it_bool process_note=false;

		if (p_note[0]==IT_CONSTANT_MAX_NOTES) { /* empty */

			/* if note field is empty, there is only one case where
			a p_note can be played.. either the channel must be inactive,
			or the instrment is different than the one previously used
			in the channel. If the conditions meet, the previos note played
			is used (as long as it exist)*/
			if ((!ch->active || ch->instrument!=(p_note[1]-1)) && ch->note<IT_CONSTANT_MAX_NOTES) {

				p_note[0]=ch->note; /* use previous channel p_note */
				process_note=it_true;
			}

		} else if (p_note[1]==0) {

			if (ch->instrument!=_IT_FIELD_EMPTY) {

				p_note[1]=ch->instrument+1;
				process_note=it_true;
			}

		} else {

			process_note=it_true;
		}

		if (process_note) {

			/* validate a sample */

			if (p->song->flags&IT_SONG_FLAG_USE_INSTRUMENTS) {

				IT_Instrument *ins=p->song->instruments[p_note[1]-1];
				if (ins) {

					sample=ins->note_sample[p_note[1]-1];
				}
			} else {

				sample=p_note[1];

			}

			if (sample>0) {

				if (p->song->samples[sample]==NULL || p->song->samples[sample]->sample_id==IT_INVALID_SAMPLE_ID) {
					process_note=it_false;
					sample=0;
				}
			}
		}
	}

	/* Carry, NNA, DCT  */

	if(p->song->flags&IT_SONG_FLAG_USE_INSTRUMENTS && ch->active && sample>0) {

		IT_Instrument *instrument_ptr=p->song->instruments[ch->instrument];

		/* carry TODO */

		/* NNA */

		if (instrument_ptr->nna_type != IT_NNA_NOTE_CUT) {

			_IT_Voice *v=&p->voices[ch->slave_voice];
			v->nna=it_true;
			ch->has_slave_voice=it_false;

			switch(instrument_ptr->nna_type) {

				case IT_NNA_NOTE_CONTINUE: {

					/* just like that.. continue.. */
				} break;
				case IT_NNA_NOTE_OFF: {

					/* set off state */
					v->note_off=it_true;
				} break;
				case IT_NNA_NOTE_FADE: {

					/* set fade state */
					v->note_fade=it_true;
				} break;
			}
		}

		/* DCT */

		if (instrument_ptr->dc_type!=IT_DCT_DISABLED) {

			int i;
			for (i=0;i<p->voice_count;i++) {
				int voice_idx=p->voice_pool[i];
				_IT_Voice *v=&p->voices[voice_idx];
				it_bool match=false;

				if (!v->active || ch->slave_voice!=voice_idx || v->master_channel!=p_channel || v->instrument!=ch->instrument)
					continue;

				switch (instrument_ptr->dc_type) {
					case IT_DCT_NOTE: {
						if (v->note==ch->note)
							match=true;
					} break;
					case IT_DCT_SAMPLE: {
						if (v->sample==ch->sample)
							match=true;
					} break;
					case Instrument::DCT_INSTRUMENT: {
						match=true;
					}break;
				}

				if (match) {
					switch (instrument_ptr->dc_action) {
						case IT_DCA_NOTE_CUT: {
							v->active=it_false;
						} break;
						case Instrument::DCA_NOTE_OFF: {

							v->note_off=it_true;
						} break;
						case Instrument::DCA_NOTE_FADE: {
							v->note_fade=it_true;
						} break;
					}
				}
			}
		}

	}

	if (sample>0) {

		it_u8 sample_idx=sample-1;
		it_bool sample_changed;
		IT_Sample *sample_ptr = p->song->samples[sample_idx];;


		/* set some p_note-start flags */
		ch->note_start_offset=0; /* unless changed by effect */
		ch->portamento_active=it_false; /* unless changed by effect */
		ch->row_has_note=it_true;
		ch->note_off=it_false;

		ch->note=p_note[0];

		/** all this was previously validated **/
		ch->instrument=p_note[1]-1;

		sample_changed=ch->sample!=sample_idx;
		ch->sample=sample_idx;

		if(p->song->flags&IT_SONG_FLAG_USE_INSTRUMENTS)
			ch->real_note=p->song->instruments[ch->instrument]->note_note[p_note[0]];
		else
			ch->real_note=p_note[0];

		/* get period from p_note */
		
		if (p->song->flags&IT_SONG_FLAG_LINEAR_PERIODS)
			ch->period=_it_player_get_linear_period( ch->real_note );
		else
			ch->period=_it_player_get_log_period( ch->real_note, sample_ptr->c5_hz );

		ch->old_period=ch->period;

		if (ch->period==0)
			ch->active=it_false;

		/* volume/pan */

		ch->volume=sample_ptr->default_volume; /* may be overriden by volume column anyway */

		if (sample_ptr->pan_enabled) {
			ch->pan=(32+(it_s16)sample_ptr->pan)*_IT_PLAYER_PAN_RIGHT/64;
		}

		if(p->song->flags&IT_SONG_FLAG_USE_INSTRUMENTS) {
			/* Pan/Volume Instrument stuff */

			IT_Instrument *instrument_ptr = p->song->instruments[ch->instrument];

			if (instrument_ptr->pan_enabled) {

				ch->pan=(32+(it_s16)instrument_ptr->pan)*_IT_PLAYER_PAN_RIGHT/64;
			}

			if (ch->pan!=_IT_PLAYER_PAN_SURROUND) {
				/* Pan Stuff */
				it_s32 new_pan=ch->pan;

				if (instrument_ptr->random_pan>0) {

					//new_pan+=((((it_s32)instrument_ptr->random_pan*2)*(_it_player_random(p)&0xFF))>>7)-128;
				}

				if (instrument_ptr->pan_pitch_separation>0) {
					new_pan+=(((it_s16)ch->real_note-(it_s16)instrument_ptr->pan_pitch_center)*instrument_ptr->pan_pitch_separation)/8;
				}

				if (new_pan<_IT_PLAYER_PAN_LEFT)
					new_pan=_IT_PLAYER_PAN_LEFT;
				else if (new_pan>_IT_PLAYER_PAN_RIGHT)
					new_pan=_IT_PLAYER_PAN_RIGHT;
			}

			if (instrument_ptr->random_volume>0) {

				it_s32 new_volume=ch->volume;
				//new_volume+=(_it_player_random(p)&0xFF))>>7)-128;
			}
		}

		ch->restart=it_true;
		ch->restart_offset=0; /* unless changed by command */
		ch->active=it_true; /* mey got unset later */		
		ch->fadeout=0xFFFF;

		_it_player_assign_slave_voice(p,p_channel);

	} else {

		ch->row_has_note=it_false;
	}

	/* process p_note off */
	if (p_note[0]==IT_CONSTANT_NOTE_OFF && ch->active) {
		/* if channels is not active, ignore */

		if(p->song->flags&IT_SONG_FLAG_USE_INSTRUMENTS) {

			IT_Instrument *instrument_ptr = p->song->instruments[ch->instrument];

			if (instrument_ptr->volume_envelope.flags&IT_ENVELOPE_ENABLED) {

				c->note_off=it_true;
			}
		}
	}

	if (p_note[0]==IT_CONSTANT_NOTE_CUT && ch->active) {

		ch->active=false;
	}

	/* Volume */
#if 0
	ch->volume_command=0; /* By default, reset volume command */

	if (p_note[2]>=0x10) {
		/* something in volume column... */

		if (p_note[2]>=0x10 && p_note[2]<=0x50) {
			/* set volume */
			ch->volume=p_note[2]-0x10;

		} else if (p_note[2]>=0xC0 && p_note[2]<=0xCF) {
			/* set pan */
			ch->pan = (p_note[2]-0xC0) << 4;
		} else {

			ch->volume_command=p_note[2];
		}
	}
#endif
	/* Command / Parameter */

	ch->command=p_note[3];
	ch->parameter=p_note[4];

	ch->note_delay=0; /* force p_note delay to zero */

}
static void _it_player_process_notes(_IT_PlayerData *p) {

	int i;
	IT_Pattern *pattern;

	if (p->song->patterns[p->current_pattern]==NULL)
		return; /* do nothing */

	pattern = p->song->patterns[p->current_pattern];

	if (p->decompressor.restart || p->current_pattern != p->decompressor.current_pattern || p->current_row < p->decompressor.current_row) {
		/* Determine wether the decompressor must be restarted */
		for(i=0;i<IT_CONSTANT_MAX_CHANNELS;i++) {
			/* clear the decompressor caches */
			p->decompressor.chan_mask[i]=0;
			p->decompressor.last_value[i][0]=0;
			p->decompressor.last_value[i][1]=0;
			p->decompressor.last_value[i][2]=0;
			p->decompressor.last_value[i][3]=0;
			p->decompressor.last_value[i][4]=0;
		}

		p->decompressor.current_pattern=p->current_pattern;
		p->decompressor.current_row=0;
		p->decompressor.data_pos=0;
	}


	/* decompress notes */

	while( p->decompressor.current_row <= p->current_row && p->decompressor.data_pos < pattern->data_size) {


		it_u8 aux_byte=pattern->data[p->decompressor.data_pos++];
		it_u8 flag=aux_byte;
		it_u8 channel=0;
		it_u8 mask;
		it_u8 note[5]={120,0,255,0,0};

		if ( flag==0 ) {

			p->decompressor.current_row++;
			continue;
		}

		channel=(flag-1) & 63;

		if ( flag & 128 ) {

			aux_byte=pattern->data[p->decompressor.data_pos++];
			p->decompressor.chan_mask[channel]=aux_byte;
		}

		mask=p->decompressor.chan_mask[channel];

		if ( mask&1 ) { /*read note*/

			note[0]=pattern->data[p->decompressor.data_pos++];
			p->decompressor.last_value[channel][0]=note[0];
		}


		if ( mask&2 ) { /*read instrument*/

			note[1]=pattern->data[p->decompressor.data_pos++];
			p->decompressor.last_value[channel][1]=note[1];
		}
		if ( mask&4 ) { /* read volume */

			note[2]=pattern->data[p->decompressor.data_pos++];
			p->decompressor.last_value[channel][2]=note[2];
		}
		if ( mask&8 ) { /* read cmd/param */

			note[3]=pattern->data[p->decompressor.data_pos++];
			p->decompressor.last_value[channel][3]=note[3];

			note[4]=pattern->data[p->decompressor.data_pos++];

			p->decompressor.last_value[channel][4]=note[4];
		}

		/* apply masks */

		if ( mask&16 ) {

			note[0]=p->decompressor.last_value[channel][0];
		}

		if ( mask&32 ) {

			note[1]=p->decompressor.last_value[channel][1];
		}
		if ( mask&64 ) {

			note[2]=p->decompressor.last_value[channel][2];
		}
		if ( mask&128 ) {

			note[3]=p->decompressor.last_value[channel][3];
			note[4]=p->decompressor.last_value[channel][4];
		}

		if (p->current_row==p->decompressor.current_row) {
			/* legal to parse row */

			_it_player_process_note(p,channel,note);
		}
	}
}

static void _it_player_process_effects(_IT_PlayerData *p) {



}


static void _it_player_process_envelope( _IT_Voice *p_voice, _IT_EnvelopeProcess *p_env_process, IT_Envelope *p_envelope) {

	it_s16 env_len;

	if ( !(p_envelope->flags&IT_ENVELOPE_ENABLED))
		return;
	if ( p_env_process->done ) /* Envelope is finished */
		return;

	env_len=p_envelope->point_count;


	/* compute envelope first */

	if (p_envelope->flags&IT_ENVELOPE_SUSTAIN_ENABLED && !ch->note_off ) {
		/* if sustain looping */
		if (p_env_process->current_point >= p_envelope->sustain_loop_end_index ) {

			p_env_process->current_point=p_envelope->sustain_loop_begin_index;
			p_env_process->tick=0;
		}
	} else if ( p_envelope->flags&IT_ENVELOPE_LOOP_ENABLED ) {
		/* else if loop enabled */

		if (p_env_process->current_point >= p_envelope->loop_end_index ) {

			p_env_process->current_point=p_envelope->loop_begin_index;
			p_env_process->tick=0;
		}
	}

	if ( p_env_process->current_point >= (env_len-1) && ( p_env_process->tick > 0) ) {
		/* envelope is terminated. note tick>0 instead of ==0, as a clever
		   trick to know for certain when the envelope ended */

		p_env_process->done=it_true;;
		p_env_process->current_point=env_len-1;
		if (env_len==0)
			return; /* a bug, don't bother with it */
	}

	{ /* process the envelope */

		it_s16 node_len=(p_env_process->current_point >= (env_len-1)) ? 0 : (p_envelope->points[ p_env_process->current_point+1 ].offset - p_envelope->points[ p_env_process->current_point ].offset);

		if (node_len==0 || ( p_env_process->tick==0)) {
			/* don't interpolate */
			p_env_process->value=p_envelope->points[ p_env_process->current_point ].value;
		} else {

			it_s16 v1=IT_ENV_VALUE(p_envelope->points[ p_env_process->current_point ]);
			it_s16 v2=IT_ENV_VALUE(p_envelope->points[ p_env_process->current_point+1 ]);
			it_s16 r=v1 + p_env_process->tick * ( v2-v1 ) / node_len;
			p_env_process->value=r;
		}

		/* increment */
		if (node_len) {
			p_env_process->tick++;
			if (p_env_process->tick>=node_len) {

				p_env_process->tick=0;
				p_env_process->current_point++;
			}
		}

	}
}

static void _it_player_update_voices(_IT_PlayerData *p) {

	int i;

	for(i=0;i<p->voice_count;i++) {

		int vidx = p->voice_pool[i];
		_IT_Voice *v=p->voices[vidx];

		if (!v->active) {
			/* voice died, stop and remove */

			p->instance->mixer->voice_stop( p->instance->mixer, vidx );

			if (i<(p->voice_count-1)) {

				it_u8 aux=p->voice_pool[p->voice_count-1];
				p->voice_pool[p->voice_count-1]=vidx;
				p->voice_pool[i]=aux;
				i--;
			}
			p->voice_count--;
			continue;
		}


		if (!v->nna && p->channels[v->master_channel].slave_voice==vidx) {
			/* copy data fom master channel if needed */

			_IT_Channel *ch=&p->channels[v->master_channel];

			v->instrument=ch->instrument;
			v->sample=ch->sample;

			v->note_off=ch->note_off;

			v->volume=ch->volume;
			v->pan=ch->pan;
			v->period=ch->period;
			v->volume=ch->volume;
			v->channel_volume=ch->channel_volume;
			v->chorus=ch->chorus;
			v->reverb=ch->reverb;
		}

		_it_player_process_envelope(&v->volume_envelope);
		_it_player_process_envelope(&v->pan_envelope);
		_it_player_process_envelope(&v->pitch_filter_envelope);		
	}

}

static void _it_player_update_mixer(_IT_PlayerData *p) {

	for(i=0;i<p->voice_count;i++) {

		int vidx = p->voice_pool[i];
		_IT_Voice *v=p->voices[vidx];



	}
	p->instance->mixer->set_process_callback_interval( p->instance->mixer, 2500000/p->current_tempo );
}

static it_u16 _it_player_get_pattern_length(_IT_PlayerData *p,it_u16 p_pattern) {


	if (p->song->patterns[p_pattern])
		return p->song->patterns[p_pattern]->rows;
	return 64; /* unallocated patterns default to 64 */
}

static it_u8 _it_player_validate_order(_IT_PlayerData *p, it_u8 p_next) {

	int loop=0;

	while(p->song->order_list[p_next]>=255) {

		if (p->song->order_list[p_next]==IT_CONSTANT_ORDER_EMPTY) {
			p_next=p->song->restart_pos;
			if (loop==1) {
				p->active=0; /* stop player, things got fucked up */
				return p_next;
			}
			loop++;
		}

		if (p->song->order_list[p_next]==IT_CONSTANT_ORDER_SKIP) {
			if (p_next==255) {
				p_next=p->song->restart_pos;
				if (loop==1) {
					p->active=0; /* stop player, things got fucked up */
					return  p_next;
				}
				loop++;
			} else {
				p_next++;
			}
		}

	}

	return p_next;

}

static void _it_player_process_tick(void *p_userdata) {


	_IT_PlayerData *p=(_IT_PlayerData*)p_userdata;

	if (!p) {
		_IT_ERROR_PRINT("Player Unconfigured (missing player)!");
		return;
	}

	if (!p->song) {

		return; /* if song set is null, don't do a thing */
	}
	if (!p->active) {

		return;
	}

	/* Check Ticks */

	if (p->tick_counter >= (p->current_speed+p->pattern_delay)) {

		/* Tick Reaches Zero, process row */

		p->tick_counter=0;
		p->pattern_delay=0;

		/* change order, as requested by some command */
		if (p->force_next_order) {

			p->current_order=_it_player_validate_order(p,p->forced_next_order);
			p->current_row=p->forced_next_order_row;
			p->current_pattern=p->song->order_list[p->current_order];

			p->force_next_order=it_false;
			p->forced_next_order_row=0;

		}

		/** process a row of nnotes **/
		_it_player_process_notes(p);

		/* increment row and check pattern/order changes */
		p->current_row++;

		if (p->current_row>=_it_player_get_pattern_length( p, p->current_pattern )) {

			p->current_row=0;
			p->current_order=_it_player_validate_order(p, p->current_order+1 );

			p->current_pattern=p->song->order_list[p->current_order];
		}

	}

	/** PROCESS EFFECTS AND ENVELOPES**/

	_it_player_process_effects_and_envelopes(p);

	/** UPDATE VOICES **/

	_it_player_update_voices(p);

	/** UPDATE MIXER **/

	_it_player_update_mixer(p);

	/** DECREMENT TICK */
	p->tick_counter++;

	printf("playing row %i, order %i\n",p->current_row,p->current_order);
}


static void _it_player_reset(_IT_PlayerData *p) {


	int i;

	for (i=0;i<IT_CONSTANT_MAX_CHANNELS;i++) {

		_it_zero_mem( &p->channels[i], sizeof( _IT_Channel) );
		p->channels[i].pan=(p->song->channels[i].pan&IT_CHANNEL_FLAG_SURROUND)?_IT_PLAYER_PAN_SURROUND:(((int)p->song->channels[i].pan+32)*255/64);
		p->channels[i].channel_volume=p->song->channels[i].volume;
		p->channels[i].reverb=p->song->channels[i].reverb;
		p->channels[i].chorus=p->song->channels[i].chorus;
	}

	for (i=0;i<_IT_PLAYER_CONSTANT_MAX_VOICES;i++) {

		_it_zero_mem( &p->channels[i], sizeof( _IT_Channel) );
		p->voice_pool[i]=i;
	}

	p->voice_count=0;

	if (p->song) {

		p->current_speed=p->song->speed;
		p->current_tempo=p->song->tempo;
	} else {

		p->current_speed=5;
		p->current_tempo=125;

	}

	p->tick_counter=p->current_speed; /* so it starts on a row */

	/* Position */

	p->current_order=0;
	p->current_row=0;
	p->pattern_delay=0;

	if (p->song) {
		p->current_pattern=p->song->order_list[0];
	}

	p->force_next_order=it_false;
	p->forced_next_order=0;
	p->forced_next_order_row=0;
	p->active=it_false;
	p->global_volume=64; /* 0x40 */
	p->active=it_false;
	p->random_seed=1234567;

	p->instance->mixer->set_process_callback( p->instance->mixer, _it_player_process_tick, p );
	p->instance->mixer->set_process_callback_interval( p->instance->mixer, 2500000/p->current_tempo );
}

/** PLAYER STUFF **/


IT_Player* it_player_create(IT_Instance *p_instance) {

	_IT_Instance *instance = (_IT_Instance*)p_instance->_private;
	_IT_PlayerData *p = instance->memory->alloc( sizeof(_IT_PlayerData), IT_MEMORY_ALLOC_PLAYER );
	_it_zero_mem(p,sizeof(_IT_PlayerData));
	p->_player._private=(IT_Player*)p;
	p->instance=instance;
	p->voice_max=instance->mixer->get_voice_count(instance->mixer);
	p->voice=(_IT_Voice*)instance->memory->alloc( sizeof(_IT_Voice)*p->voice_max, IT_MEMORY_ALLOC_PLAYER );
	p->voice_pool=(it_u8*)instance->memory->alloc( sizeof(it_u8)*p->voice_max, IT_MEMORY_ALLOC_PLAYER );
	return &p->_player;
}

void it_player_free(IT_Player *p_player) {

	_IT_PlayerData *p;
	if (p_player->_private!=(void*)p_player) {
		_IT_ERROR_PRINT("Corrupter Player Pointer (don't copy IT_Player struct).");
		return;
	}

	p = (_IT_PlayerData*)p_player->_private;
	_it_player_reset(p);
	p->instance->memory->free(p, IT_MEMORY_ALLOC_PLAYER );

}

void it_player_set_song(IT_Player *p_player, IT_Song *p_song) {

	_IT_PlayerData *p;

	if (p_player->_private!=(void*)p_player) {
		_IT_ERROR_PRINT("Corrupter Player Pointer (don't copy IT_Player struct).");
		return;
	}

	p = (_IT_PlayerData*)p_player->_private;
	p->song=p_song;
	_it_player_reset(p);
}

void it_player_play(IT_Player *p_player) {

	_IT_PlayerData *p;

	if (p_player->_private!=(void*)p_player) {
		_IT_ERROR_PRINT("Corrupter Player Pointer (don't copy IT_Player struct).");
		return;
	}

	p = (_IT_PlayerData*)p_player->_private;
	p->active=it_true;
}

void it_player_pause(IT_Player *p_player) {

	_IT_PlayerData *p;

	if (p_player->_private!=(void*)p_player) {
		_IT_ERROR_PRINT("Corrupter Player Pointer (don't copy IT_Player struct).");
		return;
	}

	p = (_IT_PlayerData*)p_player->_private;
	p->active=it_false;
}

void it_player_stop(IT_Player *p_player) {

	_IT_PlayerData *p;

	if (p_player->_private!=(void*)p_player) {
		_IT_ERROR_PRINT("Corrupter Player Pointer (don't copy IT_Player struct).");
		return;
	}

	p = (_IT_PlayerData*)p_player->_private;
	_it_player_reset(p);

}


/** SONG **/


IT_Song *it_song_create(IT_Instance *p_instance) {

	int i;
	_IT_Instance *instance = (_IT_Instance*)p_instance->_private;

	IT_Song *song = (IT_Song*)instance->memory->alloc(sizeof(IT_Song),IT_MEMORY_ALLOC_SONG_HEADER);

	song->name[0]=0;
	song->message=NULL;
	song->restart_pos=0;
	song->flags=IT_SONG_FLAG_STEREO|IT_SONG_FLAG_LINEAR_PERIODS;
	song->tempo=125;
	song->speed=6;
	song->row_hl_maj=16;
	song->row_hl_min=4;
	song->global_volume=128;

	song->reverb_mode=IT_REVERB_MODE_DISABLED;

	for(i=0;i<IT_CONSTANT_MAX_CHANNELS;i++) {
		song->channels[i].volume=64;
		song->channels[i].pan=0;
		song->channels[i].flags=0;
		song->channels[i].chorus=0;
		song->channels[i].reverb=0;
	}

	song->channel_count=IT_CONSTANT_MAX_CHANNELS;

	for(i=0;i<IT_CONSTANT_MAX_PATTERNS;i++) {
		song->patterns[i]=NULL;
	}

	for(i=0;i<IT_CONSTANT_MAX_SAMPLES;i++) {
		song->samples[i]=NULL;
	}

	for(i=0;i<IT_CONSTANT_MAX_INSTRUMENTS;i++) {
		song->instruments[i]=NULL;
	}

	for(i=0;i<IT_CONSTANT_MAX_ORDERS;i++) {
		song->order_list[i]=IT_CONSTANT_ORDER_EMPTY;
	}

	return song;
}

void it_song_free_music( IT_Instance *p_instance, IT_Song *p_song ) {

	int i;
	_IT_Instance *instance = (_IT_Instance*)p_instance->_private;

	if (p_song->message)
		instance->memory->free(p_song->message,IT_MEMORY_ALLOC_MESSAGE);

	for(i=IT_CONSTANT_MAX_PATTERNS-1;i>=0;i--) {
		if (p_song->patterns[i]) {
			instance->memory->free(p_song->patterns[i],IT_MEMORY_ALLOC_PATTERN);
			p_song->patterns[i]=NULL;
		}
	}
}

void it_song_free_instruments( IT_Instance *p_instance, IT_Song *p_song ) {

	int i;
	_IT_Instance *instance = (_IT_Instance*)p_instance->_private;

	for(i=IT_CONSTANT_MAX_INSTRUMENTS-1;i>=0;i--) {
		if (p_song->instruments[i]) {
			instance->memory->free(p_song->instruments[i],IT_MEMORY_ALLOC_INSTRUMENT);
			p_song->instruments[i]=NULL;
		}
	}

	for(i=IT_CONSTANT_MAX_SAMPLES-1;i>=0;i--) {
		if (p_song->samples[i]) {

			if (p_song->samples[i]->sample_id!=IT_INVALID_SAMPLE_ID) {

				instance->mixer->sample_free(instance->mixer,p_song->samples[i]->sample_id);
			}
			instance->memory->free(p_song->samples[i],IT_MEMORY_ALLOC_SAMPLE);
			p_song->samples[i]=NULL;
		}
	}
}

void it_song_free(IT_Instance *p_instance,IT_Song *p_song) {

	_IT_Instance *instance = (_IT_Instance*)p_instance->_private;
	it_song_free_instruments(p_instance,p_song);
	it_song_free_music(p_instance,p_song);

	instance->memory->free(p_song, IT_MEMORY_ALLOC_SONG_HEADER );
}


